Search and Filter Application in React JS
A Search and Filter Application is a React JS project that allows users to find specific information and narrow down displayed results based on search text, categories, prices, status, or other criteria. It is commonly used in e-commerce websites, product catalogs, student directories, employee management systems, dashboards, and job portals.
React JS Training Course: JustAcademy React JS Training Course
Course Demo: Register for React JS Course Demo
1. Learning Objectives
After completing this topic, learners will be able to:
- Understand search and filtering functionality in React JS.
- Create functional components using JSX.
- Manage search input and filter selections using
useState().
- Use JavaScript
filter(), map(), and includes() methods.
- Implement case-insensitive search.
- Filter data by category and other conditions.
- Combine multiple search and filter conditions.
- Display matching results dynamically.
- Handle empty search results and reset filters.
- Create a responsive interface using CSS.
2. What Is a Search and Filter Application?
A Search and Filter Application helps users find relevant records from a collection of data. Search generally matches text entered by the user, while filtering narrows the collection according to selected conditions.
Example
Imagine an online store displaying products such as laptops, mobile phones, headphones, and watches. A user can type "phone" into the search field and select the Electronics category. The application then displays only the products that match both conditions.
Search vs. Filter
| Feature |
Search |
Filter |
| Purpose |
Finds records matching a search query. |
Narrows records using selected criteria. |
| Input |
Usually a text field. |
Dropdown, checkbox, radio button, or range control. |
| Example |
Search for "Laptop". |
Choose the Electronics category. |
| Result |
Shows records matching the text. |
Shows records satisfying the selected conditions. |
3. Features of the Application
- Search by name: Find products or records using text.
- Category filter: Display items from a selected category.
- Price filter: Display products within a selected price range.
- Combined filtering: Apply search and category conditions together.
- Dynamic results: Update the list when input or filters change.
- Result counter: Display the number of matching items.
- Empty state: Show a message when no items match.
- Clear filters: Reset search text and filter selections.
- Responsive layout: Adapt the interface to mobile and desktop screens.
4. Technologies Used
| Technology |
Purpose |
| React JS |
Builds the interactive user interface. |
| JavaScript |
Processes search queries and filtering conditions. |
| JSX |
Defines the interface using HTML-like syntax. |
| HTML |
Provides input fields, dropdowns, buttons, and result containers. |
| CSS |
Styles the application and controls the layout. |
useState() |
Stores search text and filter selections. |
filter() |
Returns records that satisfy the selected conditions. |
map() |
Renders the filtered records as UI elements. |
includes() |
Checks whether text contains a specified substring. |
5. Prerequisites
Before creating the application, learners should understand:
- HTML input fields, labels, buttons, and containers.
- CSS selectors, flexbox, grid, and responsive design.
- JavaScript variables, functions, arrow functions, arrays, and objects.
- Array methods such as
filter() and map().
- String methods such as
includes() and toLowerCase().
- React functional components, JSX, event handling, and Hooks.
6. Understanding Search State in React
In React, state stores values that can change during the application's lifecycle. A search field can be connected to state so that React knows the current query and updates the displayed results whenever the query changes.
import { useState } from 'react';
function SearchExample() {
const [searchTerm, setSearchTerm] = useState('');
return (
<div>
<input
type="text"
value={searchTerm}
onChange={event => setSearchTerm(event.target.value)}
placeholder="Search items"
/>
<p>You searched for: {searchTerm}</p>
</div>
);
}
export default SearchExample;
Explanation
searchTerm stores the text entered by the user.
setSearchTerm() updates the search state.
value={searchTerm} makes the input a controlled input.
onChange updates the state when the user types.
- React re-renders the component to display the latest value.
7. Creating a React Project
Create a new React project using Vite if you do not already have one. Ensure Node.js and npm are installed.
npm create vite@latest search-filter-app -- --template react
cd search-filter-app
npm install
npm run dev
Open the local URL displayed in your terminal to view the application.
Project Structure
search-filter-app/
├── public/
├── src/
│ ├── App.jsx
│ ├── App.css
│ ├── index.css
│ └── main.jsx
├── package.json
└── index.html
App.jsx contains the search and filter logic.
App.css contains application-specific styling.
index.css contains global CSS rules.
main.jsx renders the React application.
package.json contains project dependencies and scripts.
8. Complete Search and Filter Application
The following example creates a product catalog with search by product name, category filtering, maximum-price filtering, result counts, and a Clear Filters button.
Step 1: Write App.jsx
import { useState } from 'react';
import './App.css';
const products = [
{ id: 1, name: 'Laptop Pro', category: 'Electronics', price: 65000 },
{ id: 2, name: 'Smartphone X', category: 'Electronics', price: 30000 },
{ id: 3, name: 'Wireless Headphones', category: 'Electronics', price: 2500 },
{ id: 4, name: 'Running Shoes', category: 'Fashion', price: 3500 },
{ id: 5, name: 'Cotton T-Shirt', category: 'Fashion', price: 800 },
{ id: 6, name: 'Office Chair', category: 'Furniture', price: 7500 },
{ id: 7, name: 'Study Desk', category: 'Furniture', price: 12000 },
{ id: 8, name: 'Smart Watch', category: 'Electronics', price: 5000 }
];
const categories = [
'All',
'Electronics',
'Fashion',
'Furniture'
];
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const [category, setCategory] = useState('All');
const [maxPrice, setMaxPrice] = useState('100000');
const filteredProducts = products.filter(product => {
const matchesSearch = product.name
.toLowerCase()
.includes(searchTerm.trim().toLowerCase());
const matchesCategory =
category === 'All' || product.category === category;
const matchesPrice =
maxPrice === '' || product.price <= Number(maxPrice);
return matchesSearch && matchesCategory && matchesPrice;
});
function clearFilters() {
setSearchTerm('');
setCategory('All');
setMaxPrice('100000');
}
return (
<main className="app-container">
<header className="page-header">
<h1>Search and Filter Products</h1>
<p>Find products that match your preferences.</p>
</header>
<section className="filter-panel" aria-label="Product filters">
<div className="form-field">
<label htmlFor="search">Search products</label>
<input
id="search"
type="search"
value={searchTerm}
onChange={event => setSearchTerm(event.target.value)}
placeholder="Enter a product name"
/>
</div>
<div className="form-field">
<label htmlFor="category">Category</label>
<select
id="category"
value={category}
onChange={event => setCategory(event.target.value)}
>
{categories.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</div>
<div className="form-field">
<label htmlFor="max-price">Maximum price (₹)</label>
<input
id="max-price"
type="number"
min="0"
value={maxPrice}
onChange={event => setMaxPrice(event.target.value)}
placeholder="Enter maximum price"
/>
</div>
<button type="button" onClick={clearFilters}>
Clear Filters
</button>
</section>
<section aria-live="polite">
<p className="result-count">
Showing {filteredProducts.length} of {products.length} products
</p>
{filteredProducts.length === 0 ? (
<p className="empty-message">
No products found. Try changing your search or filters.
</p>
) : (
<div className="product-grid">
{filteredProducts.map(product => (
<article className="product-card" key={product.id}>
<h2>{product.name}</h2>
<p className="product-category">
Category: {product.category}
</p>
<p className="product-price">
₹{product.price.toLocaleString('en-IN')}
</p>
</article>
))}
</div>
)}
</section>
</main>
);
}
Step 2: Write App.css
.app-container {
max-width: 1100px;
margin: 40px auto;
padding: 24px;
font-family: Arial, sans-serif;
color: #1f2937;
}
.page-header {
text-align: center;
margin-bottom: 28px;
}
.page-header h1 {
margin-bottom: 8px;
}
.page-header p {
color: #6b7280;
}
.filter-panel {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
align-items: end;
gap: 16px;
padding: 20px;
margin-bottom: 22px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-field label {
font-size: 14px;
font-weight: bold;
}
.form-field input,
.form-field select {
width: 100%;
min-width: 0;
padding: 11px;
border: 1px solid #d1d5db;
border-radius: 7px;
font-size: 15px;
background: #ffffff;
}
button {
padding: 12px 16px;
border: none;
border-radius: 7px;
background: #2563eb;
color: #ffffff;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #1d4ed8;
}
button:focus-visible,
input:focus-visible,
select:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 2px;
}
.result-count {
margin: 20px 0;
font-weight: bold;
}
.product-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.product-card {
padding: 20px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.05);
}
.product-card h2 {
margin-top: 0;
font-size: 19px;
}
.product-category {
color: #6b7280;
}
.product-price {
font-size: 18px;
font-weight: bold;
color: #2563eb;
}
.empty-message {
padding: 28px;
text-align: center;
background: #ffffff;
border-radius: 10px;
color: #6b7280;
}
@media (max-width: 850px) {
.filter-panel {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.product-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 520px) {
.app-container {
margin: 16px auto;
padding: 14px;
}
.filter-panel,
.product-grid {
grid-template-columns: 1fr;
}
}
Step 3: Add Global CSS
In src/index.css, add the following basic styles:
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: #f3f4f6;
}
Step 4: Run the Application
- Save all files.
- Run
npm run dev in the terminal.
- Open the local URL displayed by Vite.
- Enter a product name in the search field.
- Select a category from the dropdown.
- Enter a maximum price to narrow the results.
- Click Clear Filters to restore the default settings.
- Try a search that matches no products to see the empty-state message.
9. Understanding the filter() Method
The JavaScript filter() method creates a new array containing only those elements that pass a specified condition. In a React application, it is often used to calculate which items should appear in the UI.
const numbers = [10, 20, 30, 40, 50];
const filteredNumbers = numbers.filter(
number => number > 25
);
console.log(filteredNumbers);
// [30, 40, 50]
In the product application, filter() checks each product against the search text, category, and maximum price conditions.
10. Implementing Case-Insensitive Search
A search should normally match text regardless of whether the user types uppercase or lowercase letters. Use toLowerCase() to normalize both the search query and the product name.
const query = searchTerm.trim().toLowerCase();
const matches = products.filter(product =>
product.name.toLowerCase().includes(query)
);
For example, searching for "LAPTOP", "Laptop", or "laptop" returns the same matching product. Trimming removes unnecessary whitespace at the beginning and end of the query.
11. Filtering by Multiple Conditions
A search interface often needs to apply several conditions simultaneously. The logical AND operator (&&) ensures that a record must satisfy every required condition.
const filteredProducts = products.filter(product => {
const matchesName = product.name
.toLowerCase()
.includes(searchTerm.trim().toLowerCase());
const matchesCategory =
category === 'All' || product.category === category;
const matchesPrice =
maxPrice === '' || product.price <= Number(maxPrice);
return matchesName && matchesCategory && matchesPrice;
});
How it works:
matchesName checks whether the product name contains the search query.
matchesCategory allows every category when All is selected or matches the chosen category.
matchesPrice allows every price when the maximum-price field is empty or checks the numeric limit.
- The final expression returns true only when all three conditions pass.
When several filters are combined with AND, a product must satisfy all of them to appear in the result.
12. Searching Across Multiple Fields
Sometimes users need to search by more than one property, such as product name and category. You can combine these fields in the search condition.
const query = searchTerm.trim().toLowerCase();
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(query) ||
product.category.toLowerCase().includes(query)
);
The logical OR operator (||) allows a product to match either its name or its category. You can extend this approach to include descriptions, brands, or other searchable fields.
13. Adding a Minimum Price Filter
A product catalog can include both minimum and maximum price inputs.
const [minPrice, setMinPrice] = useState('0');
const [maxPrice, setMaxPrice] = useState('100000');
const filteredProducts = products.filter(product => {
const matchesMin =
minPrice === '' || product.price >= Number(minPrice);
const matchesMax =
maxPrice === '' || product.price <= Number(maxPrice);
return matchesMin && matchesMax;
});
To complete this feature, add controlled number inputs for minPrice and maxPrice to the JSX. For production applications, also validate that both values are finite, non-negative, and that the minimum does not exceed the maximum.
14. Sorting Search Results
Sorting arranges matching results in a chosen order, such as price from low to high. It is different from filtering: filtering decides which records appear, while sorting decides their order.
const sortedProducts = [...filteredProducts].sort(
(a, b) => a.price - b.price
);
Copying the array before sorting avoids mutating an array that may be derived from React state. You can add a dropdown with options such as Price: Low to High and Price: High to Low.
15. Displaying Result Counts and Empty States
Showing the number of matching records helps users understand the current search results.
<p>
Showing {filteredProducts.length} of {products.length} products
</p>
{filteredProducts.length === 0 ? (
<p>No matching products found.</p>
) : (
<div>
{filteredProducts.map(product => (
<article key={product.id}>
{product.name}
</article>
))}
</div>
)}
The conditional expression displays a helpful message when no records match and displays the product cards otherwise.
16. Understanding Controlled Inputs
A controlled input receives its current value from React state and updates that state through an event handler.
<input
type="search"
value={searchTerm}
onChange={event => setSearchTerm(event.target.value)}
placeholder="Search products"
/>
Controlled inputs keep the search field synchronized with React state. The same approach can be used for dropdowns, checkboxes, and number inputs.
17. Important React Concepts Used in This Project
| Concept |
Purpose |
| Functional Components |
Organize the user interface and application logic. |
| JSX |
Defines the structure of the search interface. |
useState() |
Stores search text and filter selections. |
onChange |
Responds to input and dropdown changes. |
filter() |
Creates an array containing matching records. |
map() |
Renders matching records into UI elements. |
includes() |
Checks for substring matches. |
toLowerCase() |
Enables case-insensitive text matching. |
Logical AND (&&) |
Combines conditions that must all be true. |
Logical OR (||) |
Allows a match against one of multiple fields. |
| Conditional Rendering |
Displays results or an empty-state message. |
| Unique Keys |
Help React identify records in rendered lists. |
18. Common Mistakes and Their Solutions
| Mistake |
Problem |
Solution |
| Forgetting to update search state |
The input does not control the search query correctly. |
Use value and onChange together. |
| Case-sensitive matching |
Uppercase and lowercase searches may behave differently. |
Normalize both strings with toLowerCase(). |
| Not trimming the query |
Leading or trailing spaces can cause unexpected matches. |
Use trim() before matching. |
| Incorrect condition combination |
Some products appear despite failing a required filter. |
Review the AND and OR operators. |
| Comparing prices as strings |
Numeric comparisons may behave incorrectly if conversion is omitted. |
Convert validated input with Number(). |
| Mutating an array during sorting |
The original array may be changed unexpectedly. |
Copy the array before calling sort(). |
| Missing keys |
React may warn when rendering lists. |
Use a stable, unique identifier as the key. |
| No empty-state message |
Users may not understand why results are missing. |
Display a message when the filtered array is empty. |
| Storing unnecessary derived state |
Stored results can become inconsistent with the source data. |
Calculate filtered results from data and current filter values. |
| Invalid price values |
Negative or non-numeric values may produce unexpected results. |
Validate price input before applying the filter. |
19. Best Practices
- Use descriptive state names such as
searchTerm, category, and maxPrice.
- Keep the original product data separate from the filtered results.
- Calculate filtered results during rendering when the dataset is small and filtering is inexpensive.
- Use
useMemo() only when profiling shows that recalculating results is expensive or when stable references are needed.
- Use stable, unique keys when rendering lists.
- Use controlled inputs for search and filter controls.
- Show the number of matching results and provide clear empty-state messages.
- Validate numeric inputs and handle empty values intentionally.
- Use semantic labels for input fields and accessible focus indicators.
- Use debouncing for expensive searches or frequent API requests when appropriate.
- For large datasets, consider server-side search, pagination, or virtualization.
- Keep the interface responsive and easy to use on mobile devices.
20. Testing Checklist
- ☐ All products appear when the search field is empty and default filters are selected.
- ☐ Searching by a complete product name works.
- ☐ Searching by part of a product name works.
- ☐ Search works regardless of uppercase or lowercase letters.
- ☐ Leading and trailing spaces do not cause unexpected results.
- ☐ Selecting a category displays only matching products.
- ☐ Applying a maximum price excludes products above the limit.
- ☐ Search and category filters work together.
- ☐ Clearing filters restores the default results.
- ☐ A no-match search displays a useful message.
- ☐ The result counter updates correctly.
- ☐ Sorting does not mutate the original product array.
- ☐ The application works on mobile and desktop screens.
- ☐ Inputs and controls have accessible labels and keyboard focus indicators.
21. Mini Project Enhancement Ideas
- Multi-category filter: Allow users to select more than one category.
- Brand filter: Filter products by manufacturer or brand.
- Minimum and maximum price: Add a complete price-range filter.
- Sorting options: Sort by name, price, or rating.
- Search descriptions: Search both product names and descriptions.
- Clear individual filters: Reset a single filter without clearing the others.
- Pagination: Split large result lists into pages.
- API integration: Retrieve product data from a remote API.
- Debounced search: Delay expensive searches until the user pauses typing.
- Loading and error states: Display messages while fetching data or when a request fails.
- Favorites: Allow users to save products to a favorites list.
- Responsive product cards: Add images and improved layouts for different screen sizes.
22. Interview Questions and Answers
Q1. What is a Search and Filter Application?
Answer: It is an application that helps users find records by matching search text and narrowing results according to selected conditions.
Q2. What is the difference between search and filter?
Answer: Search usually matches a query against text fields, while filtering narrows results according to conditions such as category, price, or status.
Q3. Which React Hook is used to store the search query?
Answer: The useState() Hook is commonly used to store and update the search query.
Q4. What does the filter() method do?
Answer: It creates a new array containing the elements that satisfy a specified condition.
Q5. What is the purpose of map()?
Answer: It transforms each item in an array into a new value, such as a React element for displaying a product card.
Q6. How can you make a search case-insensitive?
Answer: Convert both the search query and the target text to lowercase before comparing them.
Q7. How do you combine multiple filters?
Answer: Create a Boolean condition for each filter and combine the required conditions using logical operators such as && and ||.
Q8. Why use controlled inputs?
Answer: Controlled inputs keep the displayed field value synchronized with React state.
Q9. Why should the original data be preserved?
Answer: Keeping the source data unchanged allows users to change or clear filters without losing records from the original collection.
Q10. How do you display a no-results message?
Answer: Check whether the filtered array has a length of zero and conditionally display a message.
Q11. What is debouncing?
Answer: Debouncing delays an operation until a specified period has passed without another triggering event. It is useful for reducing frequent expensive searches or API requests while a user types.
Q12. When should search be handled on the server?
Answer: Server-side search is useful for large datasets, database queries, access-controlled data, or cases where downloading every record to the browser would be inefficient.
Q13. Why should each rendered product have a key?
Answer: A stable key helps React identify each item when a list is updated, filtered, or reordered.
Q14. What is the difference between filtering and sorting?
Answer: Filtering determines which items are included, while sorting determines the order in which those items appear.
Q15. Is useMemo() always necessary for filtering?
Answer: No. Simple filtering is usually fine without it. Use memoization when the calculation is expensive or profiling demonstrates a benefit.
23. Practice Exercises
- Create a search field that filters a list of student names.
- Add a dropdown to filter students by course.
- Add a category filter to the product catalog.
- Implement a minimum and maximum price filter.
- Allow searching by both product name and category.
- Add sorting by price from low to high and high to low.
- Add a button to clear all filters.
- Display the number of matching records.
- Add pagination to the results.
- Fetch data from an API and implement search, loading, and error states.
24. Quick Revision
| Concept |
Key Point |
| Search |
Finds records matching a query. |
| Filter |
Narrows records according to conditions. |
useState() |
Stores search text and filter selections. |
filter() |
Returns matching array elements. |
map() |
Renders results into UI elements. |
includes() |
Checks for a substring match. |
toLowerCase() |
Normalizes text for case-insensitive matching. |
&& |
Requires all combined conditions to be true. |
|| |
Allows at least one alternative condition to be true. |
| Controlled Input |
Synchronizes the input value with React state. |
| Stable Key |
Identifies each item in a rendered list. |
| Sorting |
Changes the order of matching results. |
25. Official Documentation and Learning Resources
26. Conclusion
A Search and Filter Application is a useful React JS project for learning controlled inputs, state management, array methods, conditional rendering, and dynamic UI updates. By combining text search with category and price filters, learners can build practical interfaces used in e-commerce stores, directories, dashboards, and data-driven websites.
To learn more, visit the JustAcademy React JS Training Course or register for a React JS course demo.